package server

import (
	"cgs-server/helper"
	"cgs-server/pkg/log"
	"github.com/gorilla/websocket"
	uuid "github.com/satori/go.uuid"
	"net/http"
	"time"
)

var WebsocketHub *Hub

const (
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer
	pongWait = 60 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer.
	maxMessageSize = 512
)

var (
	newline = []byte{'\n'}
	space   = []byte{' '}
)

func init() {
	WebsocketHub = newHub()
	Get("/ws", serveWs, "建立websocket连接", None)
}

// 管理所有的客户端连接
type Hub struct {
	// Registered clients.
	clients map[string]*Client

	// Inbound messages from the clients.
	broadcast chan *BroadcastMessage

	// Register requests from the clients.
	register chan *Client

	// Unregister requests from clients.
	unregister chan *Client
}

type BroadcastMessage struct {
	Type string      `json:"type"`
	Data interface{} `json:"data"`
}

func newHub() *Hub {
	return &Hub{
		clients:    make(map[string]*Client),
		broadcast:  make(chan *BroadcastMessage),
		register:   make(chan *Client),
		unregister: make(chan *Client),
	}
}

func (h *Hub) Run() {
	for {
		select {
		case client := <-h.register:
			h.clients[client.uuid] = client
		case client := <-h.unregister:
			if _, ok := h.clients[client.uuid]; ok {
				delete(h.clients, client.uuid)
				close(client.send)
			}
		case message := <-h.broadcast:
			msg, _ := helper.ToJSON(message)
			for _, client := range h.clients {
				select {
				case client.send <- msg:
				default:
					close(client.send)
					delete(h.clients, client.uuid)
				}
			}
		}
	}
}

// Send 发送消息
func (h *Hub) Send(message *BroadcastMessage) {
	h.broadcast <- message
}

func (h *Hub) SendByUser(message *BroadcastMessage, user User) {
	msg, _ := helper.ToJSON(message)
	for _, client := range h.clients {
		if client.User == nil {
			continue
		}

		if client.User.Id == user.Id && client.User.Token != "" && client.User.Token != user.Token {
			select {
			case client.send <- msg:
			default:
				close(client.send)
				delete(h.clients, client.uuid)
			}
		}
	}
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

//
type Client struct {
	hub *Hub

	conn *websocket.Conn

	// Buffered channel of outbound messages.
	send chan []byte

	// 记录当前用户信息
	User *User

	uuid string
}

func NewWebsocketClient(hub *Hub, conn *websocket.Conn) *Client {
	uid := uuid.Must(uuid.NewV4()).String()

	return &Client{
		hub:  hub,
		conn: conn,
		send: make(chan []byte, 256),
		uuid: uid,
	}
}

type User struct {
	Id    string
	Token string
}

func (c *Client) readPump() {
	defer func() {
		c.hub.unregister <- c
		c.conn.Close()
	}()
	c.conn.SetReadLimit(maxMessageSize)
	//c.conn.SetCloseHandler(c.closeHandler)
	/*c.conn.SetReadDeadline(time.Now().Add(pongWait))
	c.conn.SetPongHandler(func(appData string) error {
		c.conn.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})*/
	for {
		_, _, err := c.conn.ReadMessage()
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure, websocket.CloseNoStatusReceived) {
				log.Errorf("error: %v", err)
			}
			break
		}
		//message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
		//c.hub.broadcast <- &BroadcastMessage{Data: message}
	}
}

func (c *Client) writePump() {
	defer func() {
		c.conn.Close()
	}()

	for {
		select {
		case message, ok := <-c.send:
			if !ok {
				// The hub closed the channel.
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}

			c.conn.WriteMessage(websocket.TextMessage, message)
		}
	}
}

func (c *Client) closeHandler(code int, text string) error {
	log.Infof("websocket client closed: %s", c.uuid)
	return nil
}

// serveWs handles websocket requests from the peer.
func serveWs(w http.ResponseWriter, r *http.Request) {
	//fmt.Printf("w's type is %T\n", w)

	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Errorf("upgrade: %s", err.Error())
		return
	}
	wsToken := r.URL.Query().Get("ws_token")
	if wsToken != "" {
		r.Header.Set("Auth-Token", wsToken)
	}

	user, _ := GetCurrentUser(r)

	client := NewWebsocketClient(WebsocketHub, conn)
	if user != nil {
		usr := &User{
			Id: user.ID,
		}
		token := r.Context().Value("token")
		if token != nil {
			usr.Token = token.(string)
		}
		client.User = usr
	}

	client.hub.register <- client

	// Allow collection of memory referenced by the caller by doing all work in new goroutines.
	go client.writePump()
	go client.readPump()
}
